import re
from html import escape
from bs4 import BeautifulSoup

WHITE_ATTR_LIST = [
    'style',
    'href',
    'src',
    'rel',
    'title',
    'target',
    'start',
    'alt',
    'span',
    'rowspan',
    'colspan',
    'border',
]
EXCLUDE_COLOR_LIST = ['white', 'black', '#333333']
# 字符串就使用正则， 列表则是排除列表内匹配的值
WHITE_STYLE_DICT = {
    "background": EXCLUDE_COLOR_LIST,
    'background-color': EXCLUDE_COLOR_LIST,
    'color': EXCLUDE_COLOR_LIST,
    'text-align': 'center|right',
    'font-family': '(S|s)ymbol|(W|w)ingdings|(W|w)ebdings',
    'border-collapse': 'collapse',
    'white-space': [],
    'border': 'none',
}
SINGLE_TAG_LIST = ['img', 'input', 'br', 'hr']
TRANS_TAG_DICT = {
    'b': 'strong',
}
be_merge_tag = ['span', 'strong']


def is_useful_dom(dom):
    """判断是否为可用的DOM, 然后被过滤掉"""
    return isinstance(dom, str) or (dom['tag'] in SINGLE_TAG_LIST) or dom['children']


def refactor_tag_attrs(dom_dict_attrs: dict):
    """将dom的属性(attr)重构"""
    attrs_list = []
    for attr, value in dom_dict_attrs.items():
        if not bool(value):
            continue

        if 'style' == attr:
            value = ''.join([f'{sty}:{val};' for sty, val in value.items()])

        attrs_list.append(f'{attr}="{value}"')
    return attrs_list


def add_start_tag(dom, single=False):
    attrs_item_list = refactor_tag_attrs(dom.get('attrs', dict()))
    properties = (' ' + ' '.join(attrs_item_list)) if attrs_item_list else ''
    return f"""<{dom['tag']}{properties}{'/' if single else ''}>"""


def add_end_tag(dom, single=False):
    return '' if single else f"""</{dom['tag']}>"""


def trans_tag(node):
    """转换部分标签"""
    tag_name = node.name

    if not tag_name.isalpha() and \
            not tag_name.lower().startswith('h'):  # 非正常标签
        return 'span'
    return TRANS_TAG_DICT.get(tag_name, tag_name)


def handle_dom(dom):
    """序列化dom的属性数据, 并保留需要的样式"""
    raw_attrs = dom['attrs']
    new_attrs = {}
    dom['attrs'] = new_attrs
    for attr, val in raw_attrs.items():
        if attr == 'style':
            style = {}

            for item in val.strip('; ').split(';'):
                if item.find(':') == -1:
                    continue

                item_attr, item_val = item.split(':')
                if dom['tag'] in ['td', 'table'] and item_attr in ['width', 'height']:
                    style[item_attr] = item_val
                    continue

                if item_attr in WHITE_STYLE_DICT:
                    rule = WHITE_STYLE_DICT[item_attr]

                    if isinstance(rule, str):
                        if re.search(rule, item_val):
                            style[item_attr] = item_val

                    else:
                        if item_val not in rule:
                            style[item_attr] = item_val

            val = style

        if attr in WHITE_ATTR_LIST and val:
            new_attrs[attr] = val

    return dom


def get_dom(node):
    """解析为json_dom 并转换字符实体"""
    res = {
        'tag': 'span',
        'attrs': {},
        'children': []
    }

    if isinstance(node, str):
        node = escape(node).replace(u'\xa0', '&nbsp;')
        res['children'].append(node)
    else:
        res['tag'] = trans_tag(node)
        res['attrs'] = {
            attr: escape(re.sub('\\s', '', ''.join(node[attr])))
            for attr in node.attrs
            if attr in WHITE_ATTR_LIST
        }

        if res['tag'] == 'table' and res['attrs'].get('style'):
            res['attrs']['style'] = 'border-collapse: collapse;' + res['attrs']['style']

        res['children'] = [get_dom(child) for child in node.children if child != '\n']
    res['children'] = res['children'] or ['']
    return handle_dom(res)


def dom_cleaning(dom):
    """清洗dom，将相邻标签中的数据转移到一样的标签中
    p_*: parent_* 父级
    b_*: brother_* 上一个兄弟
    c_*: current_* 当前
    """
    p_tag = dom['tag']
    p_attrs = str(dom['attrs'] or '')
    b_dom = None
    res_children = []
    for cur_dom in dom['children']:
        if isinstance(cur_dom, str):
            res_children.append(cur_dom)
            continue

        if cur_dom['tag'] in SINGLE_TAG_LIST:
            res_children.append(cur_dom)
            b_dom = cur_dom
            continue

        b_dom = b_dom or cur_dom

        b_tag = b_dom['tag']
        b_attrs = str(b_dom['attrs'] or '')

        c_tag = cur_dom['tag']
        c_attrs = str(cur_dom['attrs'] or '')
        c_is_merge_tag = c_tag in be_merge_tag

        cp_equal_attrs = c_attrs == p_attrs
        cb_equal_attrs = c_attrs == b_attrs

        dom_cleaning(cur_dom)

        # 合并父
        if c_is_merge_tag and c_tag == p_tag and (cp_equal_attrs or p_attrs and not c_attrs):
            res_children.extend(cur_dom['children'])
            cur_dom['children'] = []

        # 合并兄
        elif c_is_merge_tag and c_tag == b_tag and cb_equal_attrs and b_dom is not cur_dom:
            b_dom['children'].extend(cur_dom['children'])
            cur_dom['children'] = []

        # 不合并
        else:
            res_children.append(cur_dom)
            b_dom = cur_dom

    dom['children'] = [*filter(is_useful_dom, res_children)]


def trans_dom_to_string(dom):
    """递归地将dom对象转化为字符串"""
    res = ''
    for dom_child in dom['children']:
        if isinstance(dom_child, str):
            res += dom_child
        else:
            is_single = dom_child['tag'] in SINGLE_TAG_LIST

            res += add_start_tag(dom_child, is_single)
            res += trans_dom_to_string(dom_child)
            res += add_end_tag(dom_child, is_single)

    return res


def transfer_dom(html_string):
    html_string = html_string.replace('!msorm', '')
    bs = BeautifulSoup(html_string, 'lxml')
    dom = get_dom(bs.body)
    dom_cleaning(dom)
    return trans_dom_to_string(dom)


if __name__ == '__main__':
    text = '''
    <div class="container" id="app">
        <h1>
            上海沪源医药有限公司
        </h1>
    </div>
    <span></span><span style="color:red;background-color:blue">123</span><strong>456</strong>
    <span style="color:red;">123</span>
    <span style="color:red;">123</span><span style="color:blue">456</span>
    <span><span style="color:red;">123</span><span style="color:blue">456</span></span>
    <p>12<span style="color:red;">34</span>7<span style="color:blue;">8</span>9</p>
    <p style="text-align:center"><span style="color:red;">123</span><span style="color:blue">456</span></p>
    <p style="text-align:center">---<span style="color:red;">123</span><span style="color:blue">456</span></p>
    <span><strong><span>111</span></strong></span>
    <span><strong><span style="color:red">222</span>444</strong></span>
    <p><span style="color:red;">123</span><span>456</span></p>
    <p><span>123</span><span>456</span></p>
    <p><span><span>123</span><span>456</span></span></p>
    <span>123<span style="color:red">456</span>789</span>
    <p><span><span>123<span style="color:red">456</span>789</span></span></p>
    <p>123<span>456</span></p>
    
    
    <div>
    <table class="table table-bordered excipients-table" style="text-align: center">
          <tbody><tr style="text-align: center">
            <th rowspan="2">中文品名</th>
            <th rowspan="2">型号</th>
            <th rowspan="2">批准文号</th>
            <th rowspan="2">资质</th>
            <th rowspan="2">包装</th>
            <th colspan="2">生产商</th>
          </tr>
          <tr style="text-align: center">
            <th>产地</th>
            <th>企业名称</th>
          </tr>

           <tr>
            <td style="width: 10%">明胶</td>
            <td style="width: 15%"></td>
            <td style="width: 10%">D20080001</td>
            <td style="width: 10%">吉辅准字</td>
            <td style="width: 15%">25kg</td>
            <td style="width: 10%">中国</td>
            <td style="width: 30%">嘉利达(辽源)明胶有限公司</td>
          </tr>
           <tr>
            <td>山梨醇</td>
            <td></td>
            <td>H45020273</td>
            <td>GMP</td>
            <td>25kg</td>
            <td>中国</td>
            <td>广西南宁化学制药有限责任公司</td>
          </tr>
           <tr>
            <td>甘露醇</td>
            <td></td>
            <td>H45020272</td>
            <td>GMP</td>
            <td>25kg</td>
            <td>中国</td>
            <td>广西南宁化学制药有限责任公司</td>
          </tr>
           <tr>
            <td>甘露醇</td>
            <td>Pearlitol 25C</td>
            <td>H20110008</td>
            <td>IDL</td>
            <td>25kg  50kg/袋</td>
            <td>法国</td>
            <td>Roquette　Freres</td>
          </tr>
           <tr>
            <td>甘露醇</td>
            <td>Pearlitol 25C</td>
            <td>H20110019</td>
            <td>IDL</td>
            <td>25kg  50kg/袋</td>
            <td>法国</td>
            <td>Roquette　Freres</td>
          </tr>
           <tr>
            <td>甘露醇</td>
            <td>Pearlitol 50C</td>
            <td>H20110012</td>
            <td>IDL</td>
            <td>25kg  50kg/袋</td>
            <td>法国</td>
            <td>Roquette　Freres</td>
          </tr>
          <tr>
            <td>甘露醇</td>
            <td>Pearlitol 50C</td>
            <td>H20110009</td>
            <td>IDL</td>
            <td>25kg  50kg/袋</td>
            <td>法国</td>
            <td>Roquette　Freres</td>
          </tr>
           <tr>
            <td>甘露醇</td>
            <td>Pearlitol 160C</td>
            <td>H20110014</td>
            <td>IDL</td>
            <td>25kg  50kg/袋</td>
            <td>法国</td>
            <td>Roquette　Freres</td>
          </tr>
          <tr>
            <td>甘露醇</td>
            <td>Pearlitol 160C</td>
            <td>H20110005</td>
            <td>IDL</td>
            <td>25kg  50kg/袋</td>
            <td>法国</td>
            <td>Roquette　Freres</td>
          </tr>
          <tr>
            <td>甘露醇</td>
            <td>Pearlitol 100SD</td>
            <td>H20110010</td>
            <td>IDL</td>
            <td>25kg  50kg/袋</td>
            <td>法国</td>
            <td>Roquette　Freres</td>
          </tr>
           <tr>
            <td>甘露醇</td>
            <td>Pearlitol 100SD</td>
            <td>H20110011</td>
            <td>IDL</td>
            <td>25kg  50kg/袋</td>
            <td>法国</td>
            <td>Roquette　Freres</td>
          </tr>
           <tr>
            <td>甘露醇</td>
            <td>Pearlitol 200SD</td>
            <td>H20110016</td>
            <td>IDL</td>
            <td>25kg  50kg/袋</td>
            <td>法国</td>
            <td>Roquette　Freres</td>
          </tr>
           <tr>
            <td>甘露醇</td>
            <td>Pearlitol 200SD</td>
            <td>H20110007</td>
            <td>IDL</td>
            <td>25kg  50kg/袋</td>
            <td>法国</td>
            <td>Roquette　Freres</td>
          </tr>
           <tr>
            <td>甘露醇</td>
            <td>Pearlitol 300DC</td>
            <td>H20110020</td>
            <td>IDL</td>
            <td>25kg  50kg/袋</td>
            <td>法国</td>
            <td>Roquette　Freres</td>
          </tr>
           <tr>
            <td>甘露醇</td>
            <td>Pearlitol 300DC</td>
            <td>H20110013</td>
            <td>IDL</td>
            <td>25kg  50kg/袋</td>
            <td>法国</td>
            <td>Roquette　Freres</td>
          </tr>
          <tr>
            <td>甘露醇</td>
            <td>Pearlitol 400DC</td>
            <td>H20110006</td>
            <td>IDL</td>
            <td>25kg  50kg/袋</td>
            <td>法国</td>
            <td>Roquette　Freres</td>
          </tr>
          <tr>
            <td>甘露醇</td>
            <td>Pearlitol 400DC</td>
            <td>H20110015</td>
            <td>IDL</td>
            <td>25kg  50kg/袋</td>
            <td>法国</td>
            <td>Roquette　Freres</td>
          </tr>
           <tr>
            <td>甘露醇</td>
            <td>Pearlitol 500DC</td>
            <td>H20110018</td>
            <td>IDL</td>
            <td>25kg  50kg/袋</td>
            <td>法国</td>
            <td>Roquette　Freres</td>
          </tr>
           <tr>
            <td>甘露醇</td>
            <td>Pearlitol 500DC</td>
            <td>H20110017</td>
            <td>IDL</td>
            <td>25kg  50kg/袋</td>
            <td>法国</td>
            <td>Roquette　Freres</td>
          </tr>
        </tbody></table>
    </div>
    '''
    test = '<p><strong><span style="color: red;"></span><span>444</span></strong></p>'
    result = ''
    result += transfer_dom(text) + '\n'
    print(result)
